package com.gitee.wsl.lang.bytearray

import com.gitee.wsl.base.Objects
import com.gitee.wsl.base.Objects.equals
import com.gitee.wsl.ext.array.bytes.toByteArray
import com.gitee.wsl.ext.array.combineToByteArray
import com.gitee.wsl.ext.array.constantTimeEquals
import com.gitee.wsl.ext.array.countByteArray
import com.gitee.wsl.ext.array.endsWith
import com.gitee.wsl.ext.array.entropy
import com.gitee.wsl.ext.array.indexOf
import com.gitee.wsl.ext.array.toByteArray
import com.gitee.wsl.ext.array.bytes.toDoubleArray
import com.gitee.wsl.ext.array.bytes.toFloatArray
import com.gitee.wsl.ext.array.bytes.toIntArray
import com.gitee.wsl.ext.array.bytes.toLongArray
import com.gitee.wsl.ext.array.bytes.toShortArray
import com.gitee.wsl.ext.base.Arrays.arraycopy
import com.gitee.wsl.ext.string.binary2ByteArray
import com.gitee.wsl.io.Charset
import com.gitee.wsl.io.buffer.ByteBuffer
import com.gitee.wsl.io.buffer.peck
import com.gitee.wsl.io.decodeToString
import com.gitee.wsl.lang.ByteOrder
import com.gitee.wsl.lang.bits.BitSet
import com.gitee.wsl.lang.bytearray.Bytes.Companion.random
import com.gitee.wsl.mathematics.function.random.SecureRandom
import com.gitee.wsl.transform.encode.base.decodeBase64
import com.gitee.wsl.transform.encode.hex.hexToByteArray
import com.ionspin.kotlin.bignum.integer.BigInteger
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmOverloads
import kotlin.random.Random
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid


/**
 * "Bytes" is wrapper class for a byte-array that allows a lot of convenience operations on it:
 *
 *  * Creation from various source: arrays, primitives, parsed or random
 *  * Encoding in many formats: hex, base64, etc.
 *  * Helper functions like: indexOf, count, entropy
 *  * Transformations like: append, reverse, xor, and, resize, ...
 *  * Conversation to other types: primitives, List, object array, ByteBuffer, BigInteger, ...
 *  * Validation: built-in or provided
 *  * Making it mutable or read-only
 *
 *
 *
 * It supports byte ordering (little/big endianness).
 *
 *
 * This class is immutable as long as the internal array is not changed from outside (which can't be assured, when
 * using `wrap()`). It is possible to create a mutable version (see [MutableBytes]).
 *
 *
 * **Example:**
 * <pre>
 * Bytes b = Bytes.from(array).mutable();
 * b.not();
 * System.out.println(b.encodeHex());
</pre> *
 *
 * <h3>Comparable</h3>
 * The implemented comparator treats the bytes as signed bytes. If you want to sort, treating each byte as unsigned,
 * use [BytesTransformers.sortUnsigned].
 */
@Serializable
class Bytes @JvmOverloads internal constructor(
    private val byteArray: ByteArray,
    val byteOrder: ByteOrder,
    //factory: BytesFactory = Factory(),
) : Comparable<Bytes>, Iterable<Byte> {

    //private val factory: BytesFactory

    /* TRANSFORMER **********************************************************************************************/
    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end).
     *
     *
     * This will create a new byte array internally, so it is not suitable to use as extensive builder pattern -
     * use [ByteBuffer] or [java.io.ByteArrayOutputStream] for that.
     *
     * @param bytes to append
     * @return appended instance
     */
    fun append(bytes: Bytes): Bytes {
        return append(bytes.internalArray())
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param singleByte to append
     * @return appended instance
     */
    fun append(singleByte: Byte): Bytes {
        return append(from(singleByte))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param char2Bytes to append
     * @return appended instance
     */
    fun append(char2Bytes: Char): Bytes {
        return append(from(char2Bytes))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param short2Bytes to append
     * @return appended instance
     */
    fun append(short2Bytes: Short): Bytes {
        return append(from(short2Bytes))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param integer4Bytes to append
     * @return appended instance
     */
    fun append(integer4Bytes: Int): Bytes {
        return append(from(integer4Bytes))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param long8Bytes to append
     * @return appended instance
     */
    fun append(long8Bytes: Long): Bytes {
        return append(Companion.from(long8Bytes))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end).
     * You may use this to append multiple byte arrays without the need for chaining the [.append] call
     * and therefore generating intermediate copies of the byte array, making this approach use less memory.
     *
     * @param arrays to append
     * @return appended instance
     */
    fun append(vararg arrays: ByteArray): Bytes {
        return append(from(*arrays))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     * @param secondArray to append
     * @return appended instance
     */
    fun append(secondArray: ByteArray): Bytes {
        return transform(BytesTransformer.ConcatTransformer(secondArray))
    }

    /**
     * Creates a new instance with the current array appended to the provided data (i.e. append at the end)
     *
     *
     * If given array is null, the nothing will be appended.
     *
     * @param secondArrayNullable to append, may be null
     * @return appended instance or same if passed array is null
     */
    fun appendNullSafe(secondArrayNullable: ByteArray?): Bytes? {
        return if (secondArrayNullable == null) this else append(secondArrayNullable)
    }

    /**
     * Creates a new instance with the current array appended to the provided utf-8 encoded representation of this string
     *
     * @param stringUtf8 string used to get utf-8 bytes from
     * @return appended instance
     */
    fun append(stringUtf8: CharSequence): Bytes {
        return append(stringUtf8, Charset.UTF8)
    }

    /**
     * Creates a new instance with the current array appended to the provided string with provided encoding
     *
     * @param string  string used to get bytes from
     * @param charset encoding of provided string
     * @return appended instance
     */
    fun append(string: CharSequence, charset: Charset): Bytes {
        return transform(
            BytesTransformer.ConcatTransformer(charset.encode(string)
            )
        )
    }

    /**
     * Bitwise XOR operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param bytes must be of same length as this instance
     * @return xor'ed instance
     * @see [Bitwise operators: XOR](https://en.wikipedia.org/wiki/Bitwise_operation.XOR)
     */
    fun xor(bytes: Bytes): Bytes {
        return xor(bytes.internalArray())
    }

    /**
     * Bitwise XOR operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param secondArray must be of same length as this instance
     * @return xor'ed instance
     * @see [Bitwise operators: XOR](https://en.wikipedia.org/wiki/Bitwise_operation.XOR)
     */
    fun xor(secondArray: ByteArray): Bytes {
        return transform(
            BytesTransformer.BitWiseOperatorTransformer(
                secondArray,
                BytesTransformer.BitWiseOperatorTransformer.Mode.XOR
            )
        )
    }

    /**
     * Bitwise AND operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param bytes must be of same length as this instance
     * @return and'ed instance
     * @see [Bitwise operators: AND](https://en.wikipedia.org/wiki/Bitwise_operation.AND)
     */
    fun and(bytes: Bytes): Bytes {
        return and(bytes.internalArray())
    }

    /**
     * Bitwise AND operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param secondArray must be of same length as this instance
     * @return and'ed instance
     * @see [Bitwise operators: AND](https://en.wikipedia.org/wiki/Bitwise_operation.AND)
     */
    fun and(secondArray: ByteArray): Bytes {
        return transform(
            BytesTransformer.BitWiseOperatorTransformer(
                secondArray,
                BytesTransformer.BitWiseOperatorTransformer.Mode.AND
            )
        )
    }

    /**
     * Bitwise OR operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param bytes must be of same length as this instance
     * @return or'ed instance
     * @see [Bitwise operators: OR](https://en.wikipedia.org/wiki/Bitwise_operation.OR)
     */
    fun or(bytes: Bytes): Bytes {
        return or(bytes.internalArray())
    }

    /**
     * Bitwise OR operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param secondArray must be of same length as this instance
     * @return or'ed instance
     * @see [Bitwise operators: OR](https://en.wikipedia.org/wiki/Bitwise_operation.OR)
     */
    fun or(secondArray: ByteArray): Bytes {
        return transform(
            BytesTransformer.BitWiseOperatorTransformer(
                secondArray,
                BytesTransformer.BitWiseOperatorTransformer.Mode.OR
            )
        )
    }

    /**
     * Bitwise not operation on the whole internal byte array.
     * See the considerations about possible in-place operation in [.transform].
     *
     * @return negated instance
     * @see [Bitwise operators: NOT](https://en.wikipedia.org/wiki/Bitwise_operation.NOT)
     */
    fun not(): Bytes {
        return transform(BytesTransformer.NegateTransformer())
    }

    /**
     * Bitwise left shifting of internal byte array (i.e. `&#x3C;&#x3C;`). Unlike [BigInteger]'s implementation, this one will never
     * grow or shrink the underlying array. Either a bit is pushed out of the array or a zero is pushed in.
     *
     *
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param shiftCount how many bits (not bytes)
     * @return shifted instance
     * @see [Bit shifts](https://en.wikipedia.org/wiki/Bitwise_operation.Bit_shifts)
     */
    fun leftShift(shiftCount: Int): Bytes {
        return transform(
            BytesTransformer.ShiftTransformer(
                shiftCount,
                BytesTransformer.ShiftTransformer.Type.LEFT_SHIFT,
                byteOrder
            )
        )
    }

    /**
     * Bitwise unsigned/logical right shifting of internal byte array (i.e. `&#x3E;&#x3E;&#x3E;`). Unlike
     * [BigInteger]'s implementation, this one will never grow or shrink the underlying array. Either a bit is pushed
     * out of the array or a zero is pushed in.
     *
     *
     * See the considerations about possible in-place operation in [.transform].
     *
     * @param shiftCount how many bits (not bytes)
     * @return shifted instance
     * @see [Bit shifts](https://en.wikipedia.org/wiki/Bitwise_operation.Bit_shifts)
     */
    fun rightShift(shiftCount: Int): Bytes {
        return transform(
            BytesTransformer.ShiftTransformer(
                shiftCount,
                BytesTransformer.ShiftTransformer.Type.RIGHT_SHIFT,
                byteOrder
            )
        )
    }

    /**
     * Returns a Byte whose value is equivalent to this Byte with the designated bit set to newBitValue. Bits start to count from the LSB (i.e. Bytes.from(0).switchBit(0,true) == 1)
     *
     * @param bitPosition not to confuse with byte position
     * @param newBitValue if true set to 1, 0 otherwise
     * @return instance with bit switched
     */
    fun switchBit(bitPosition: Int, newBitValue: Boolean): Bytes {
        return transform(BytesTransformer.BitSwitchTransformer(bitPosition, newBitValue))
    }

    /**
     * Returns a Byte whose value is equivalent to this Byte with the designated bit switched.
     *
     * @param bitPosition not to confuse with byte position
     * @return instance with bit switched
     */
    fun switchBit(bitPosition: Int): Bytes {
        return transform(BytesTransformer.BitSwitchTransformer(bitPosition, null))
    }

    /**
     * Creates a new instance with a copy of the internal byte array and all other attributes.
     *
     * @return copied instance
     */
    fun copy(): Bytes {
        return transform(BytesTransformer.CopyTransformer(0, length()))
    }

    /**
     * Creates a new instance with a copy of the internal byte array and all other attributes.
     *
     * @param offset starting position in the source array
     * @param length of the new instance
     * @return copied instance
     */
    fun copy(offset: Int, length: Int): Bytes {
        return transform(BytesTransformer.CopyTransformer(offset, length))
    }

    /**
     * Reverses the internal bytes in the array (not bits in each byte)
     *
     *
     * See the considerations about possible in-place operation in [.transform].
     *
     * @return reversed instance
     */
    fun reverse(): Bytes {
        return transform(BytesTransformer.ReverseTransformer())
    }

    /**
     * Copies the specified array, truncating or padding with zeros (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain `(byte)0`.
     *
     *
     * **Modes:**
     *
     *  * [BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_ZERO_INDEX]: Resize from MSB or index 0;
     * so an array [0,1,2,3] resized to 3 will result in [0,1,2] or resized to 5 [0,1,2,3,0]
     *  * [BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH]: Resize from LSB or length;
     * so an array [0,1,2,3] resized to 3 will result in [1,2,3] or resized to 5 [0,0,1,2,3]
     *
     *
     * @param newByteLength the length of the copy to be returned
     * @param mode          from which end the length will start to count (either index 0 or length())
     * @return a copy with the desired size or "this" instance if newByteLength == current length
     */
    /**
     * Copies the specified array, truncating or padding with zeros (if necessary)
     * so the copy has the specified length.  For all indices that are
     * valid in both the original array and the copy, the two arrays will
     * contain identical values.  For any indices that are valid in the
     * copy but not the original, the copy will contain `(byte)0`.
     *
     *
     * Resize from LSB or length, so an array [0,1,2,3] resized to 3 will result in [1,2,3] or resized to 5 [0,0,1,2,3].
     * So when an 8 byte value resized to 4 byte will result in the same 32-bit integer value
     *
     * @param newByteLength the length of the copy to be returned
     * @return a copy with the desired size or "this" instance if newByteLength == current length
     */
    @JvmOverloads
    fun resize(
        newByteLength: Int,
        mode: BytesTransformer.ResizeTransformer.Mode? = BytesTransformer.ResizeTransformer.Mode.RESIZE_KEEP_FROM_MAX_LENGTH,
    ): Bytes {
        return transform(BytesTransformer.ResizeTransformer(newByteLength, mode))
    }

    /**
     * Calculates md5 on the underlying byte array and returns a byte instance containing the hash.
     * This hash algorithm SHOULD be supported by every JVM implementation (see
     * [Javadoc for MessageDigest](https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html))
     *
     *
     * **Do not use this algorithm in security relevant applications.**
     *
     * @return md5 (16 bytes) hash of internal byte array
     * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
     * @see [MD5](https://en.wikipedia.org/wiki/MD5)
     */
//    fun hashMd5(): Bytes {
//        return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_MD5)
//    }

    /**
     * Calculates sha1 on the underlying byte array and returns a byte instance containing the hash.
     * This hash algorithm SHOULD be supported by every JVM implementation (see
     * [Javadoc for MessageDigest](https://docs.oracle.com/javase/7/docs/api/java/security/MessageDigest.html))
     *
     *
     * **Do not use this algorithm in security relevant applications.**
     *
     * @return sha1 (20 bytes) hash of internal byte array
     * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
     * @see [Secure Hash Algorithm 1](https://en.wikipedia.org/wiki/SHA-1)
     */
//    fun hashSha1(): Bytes {
//        return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_SHA_1)
//    }

    /**
     * Calculates sha256 on the underlying byte array and returns a byte instance containing the hash.
     *
     * @return sha256 (32 bytes) hash of internal byte array
     * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
     * @see [Secure Hash Algorithms](https://en.wikipedia.org/wiki/Secure_Hash_Algorithms)
     */
//    fun hashSha256(): Bytes {
//        return hash(BytesTransformer.MessageDigestTransformer.ALGORITHM_SHA_256)
//    }

    /**
     * Calculates hash with provided algorithm on the underlying byte array and returns a byte instance
     * containing the hash.
     *
     * @param algorithm same format as passed to [java.security.MessageDigest.getInstance]
     * @return hash of internal byte array
     * @throws IllegalArgumentException if the message digest algorithm can not be found in the security providers
     */
//    fun hash(algorithm: String): Bytes {
//        return transform(MessageDigestTransformer(algorithm))
//    }

    /**
     * Generic transformation of this instance.
     *
     *
     * This transformation might be done in-place (i.e. without copying the internal array and overwriting its old state),
     * or on a copy of the internal data, depending on the type (e.g. [MutableBytes]) and if the operation can be done
     * in-place. Therefore, the caller has to ensure that certain side effects, which occur due to the changing of the internal
     * data, do not create bugs in his/her code. Usually immutability is preferred, but when handling many or big byte arrays,
     * mutability enables drastically better performance.
     *
     * @param transformer used to transform this instance
     * @return the transformed instance (might be the same, or a new one)
     */
    fun transform(transformer: BytesTransformer): Bytes {
        return wrap(transformer.transform(internalArray(), this.isMutable), byteOrder)
    }

    /* VALIDATORS ***************************************************************************************************/
    /**
     * Checks the content of each byte for 0 values
     *
     * @return true if not empty and only contains zero byte values
     */
    fun validateNotOnlyZeros(): Boolean {
        return validate(BytesValidator.notOnlyOf(0.toByte()))
    }

    /**
     * Applies all given validators and returns true if all of them return true (default AND concatenation).
     *
     * @param bytesValidators array of validators to check against the byte array
     * @return true if all validators return true
     */
    fun validate(vararg bytesValidators: BytesValidator): Boolean {
        return BytesValidator.and(*bytesValidators).validate(internalArray())
    }

    /* ATTRIBUTES ************************************************************************************************/
    /**
     * The byte length of the underlying byte array.
     *
     * @return byte length
     */
    fun length(): Int {
        return internalArray().size
    }

    /**
     * The bit length of the underlying byte array.
     *
     * @return the bit length
     */
    fun lengthBit(): Int {
        return length() * 8
    }

    val isEmpty: Boolean
        /**
         * Checks the internal array for emptiness.
         *
         * @return if the underlying byte array has a length of 0
         */
        get() = length() == 0

    /**
     * Get the set byte order/endianness. Default in Java is [ByteOrder.BIG_ENDIAN].
     *
     * @return either [ByteOrder.BIG_ENDIAN] or [ByteOrder.LITTLE_ENDIAN]
     * @see [Endianness](https://en.wikipedia.org/wiki/Endianness)
     */
//    fun byteOrder(): ByteOrder {
//        return byteOrder
//    }

    val isMutable: Boolean
        /**
         * Checks if instance is mutable
         *
         * @return true if mutable, i.e. transformers will change internal array
         */
        get() = false

    val isReadOnly: Boolean
        /**
         * Check if this instance is read only
         *
         * @return true if read only
         */
        get() = false

    /**
     * Checks if given byte value is contained in internal array
     *
     * @param target a primitive `byte` value
     * @return true if this Bytes instance contains the specified element
     */
    fun contains(target: Byte): Boolean {
        return indexOf(target) != -1
    }

    /**
     * Returns the index of the first appearance of the value `target` in
     * `array` from given start index 'fromIndex'.
     *
     * @param target    a primitive `byte` value
     * @param fromIndex search from this index
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists or fromIndex is gt target length.
     */
    /**
     * Returns the index of the first appearance of the value `target` in
     * `array`. Same as calling [.indexOf] with fromIndex '0'.
     *
     * @param target a primitive `byte` value
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists.
     */
    @JvmOverloads
    fun indexOf(target: Byte, fromIndex: Int = 0): Int {
        return indexOf(byteArrayOf(target), fromIndex)
    }

    /**
     * Returns the index of the first appearance of the value `target` in
     * `array` from given start index 'fromIndex' to given end index 'toIndex'.
     *
     * @param target    a primitive `byte` value
     * @param fromIndex search from this index
     * @param toIndex   search to this index
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists or fromIndex is gt target length.
     */
    fun indexOf(target: Byte, fromIndex: Int, toIndex: Int): Int {
        return indexOf(byteArrayOf(target), fromIndex, toIndex)
    }

    /**
     * Returns the start position of the first occurrence of the specified `target` within `array` from given start index 'fromIndex', or `-1`
     * if there is no such occurrence.
     *
     *
     * More formally, returns the lowest index `i` such that `java.util.Arrays.copyOfRange(array, i, i + target.length)` contains exactly
     * the same elements as `target`.
     *
     * @param subArray  the array to search for as a sub-sequence of `array`
     * @param fromIndex search from this index
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists.
     */
    /**
     * Returns the start position of the first occurrence of the specified `target` within `array`, or `-1` if there is no such occurrence.
     *
     *
     * More formally, returns the lowest index `i` such that `java.util.Arrays.copyOfRange(array, i, i + target.length)` contains exactly
     * the same elements as `target`.
     *
     * @param subArray the array to search for as a sub-sequence of `array`
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists.
     */
    @JvmOverloads
    fun indexOf(subArray: ByteArray, fromIndex: Int = 0): Int {
        return internalArray().indexOf(subArray, start = fromIndex, end = length())
        //return Util.Byte.indexOf(internalArray(), subArray, fromIndex, length())
    }

    /**
     * Returns the start position of the first occurrence of the specified `target` within `array` from given start index 'fromIndex' to given end
     * index 'toIndex', or `-1` if there is no such occurrence.
     *
     *
     * More formally, returns the lowest index `i` such that `java.util.Arrays.copyOfRange(array, i, i + target.length)` contains exactly
     * the same elements as `target`.
     *
     * @param subArray  the array to search for as a sub-sequence of `array`
     * @param fromIndex search from this index
     * @param toIndex search to this index
     * @return the least index `i` for which `array[i] == target`, or
     * `-1` if no such index exists.
     */
    fun indexOf(subArray: ByteArray, fromIndex: Int, toIndex: Int): Int {
        return internalArray().indexOf(subArray, start = fromIndex, end = toIndex)
        //return Util.Byte.indexOf(internalArray(), subArray, fromIndex, toIndex)
    }

    /**
     * Checks if the given sub array is equal to the start of given array. That is, sub array must be gt or eq
     * to the length of the internal array and `internal[i] == subArray[i]` for i=0..subArray.length-1
     *
     * @param subArray to check against the start of the internal array
     * @return true if the start of the internal array is eq to given sub array
     */
    fun startsWith(subArray: ByteArray): Boolean {
        return internalArray().indexOf(subArray) == 0
        //return Util.Byte.indexOf(internalArray(), subArray, 0, 1) == 0
    }

    /**
     * Returns the index of the last appearance of the value `target` in
     * `array`.
     *
     * @param target a primitive `byte` value
     * @return the greatest index `i` for which `array[i] == target`,
     * or `-1` if no such index exists.
     */
    fun lastIndexOf(target: Byte): Int {
        return internalArray().lastIndexOf(target)
        //return Util.Byte.lastIndexOf(internalArray(), target, 0, length())
    }

    /**
     * Checks if the given sub array is equal to the end of given array. That is, sub array must be gt or eq
     * to the length of the internal array and `internal[i] == subArray[i]` for i=subArray.length...internal.length
     *
     * @param subArray to check against the end of the internal array
     * @return true if the end of the internal array is eq to given sub array
     */
    fun endsWith(subArray: ByteArray): Boolean {
        return internalArray().endsWith(subArray)
//        val startIndex = length() - subArray.size
//        return startIndex >= 0 && Util.Byte.indexOf(
//            internalArray(),
//            subArray,
//            startIndex,
//            startIndex + 1
//        ) === startIndex
    }

    /**
     * Returns the `bit` value as boolean at the specified index. Bit index 0 is the LSB, so for example byte word
     * `1000 0000` has `bitAt(0) == false` and `bitAt(7) == true`.
     *
     * @param bitIndex the index of the `bit` value.
     * @return true if the bit at given index is set, false otherwise
     * @throws IndexOutOfBoundsException if the `bitIndex` argument is negative or not less than the length of this array in bits.
     */
    fun bitAt(bitIndex: Int): Boolean {
        //Util.Validation.checkIndexBounds(lengthBit(), bitIndex, 1, "bit")
        if (byteOrder == ByteOrder.BigEndian) {
            return ((byteAt(length() - 1 - (bitIndex / 8)).toInt() ushr bitIndex % 8) and 1) != 0
        } else {
            return ((byteAt(bitIndex / 8).toInt() ushr bitIndex % 8) and 1) != 0
        }
    }

    /**
     * Returns the `byte` value at the specified index.
     * An index ranges from `0` to `length() - 1`. The first `char` value of the sequence
     * is at index `0`, the next at index `1`, and so on, as for array indexing.
     *
     * @param index the index of the `byte` value.
     * @return the `byte` value at the specified index of the underlying byte array.
     * @throws IndexOutOfBoundsException if the `index` argument is negative or not less than the length of this array.
     */
    fun byteAt(index: Int): Byte {
        //Util.Validation.checkIndexBounds(length(), index, 1, "byte")
        return internalArray()[index]
    }

    /**
     * Returns the unsigned `byte` value at the specified index as an int.
     * An index ranges from `0` to `length() - 1`. The first `char` value of the sequence
     * is at index `0`, the next at index `1`, and so on, as for array indexing.
     *
     * @param index the index of the unsigned `byte` value.
     * @return the unsigned `byte` value at the specified index of the underlying byte array as type 4 byte integer
     * @throws IndexOutOfBoundsException if the `index` argument is negative or not less than the length of this array.
     */
    fun unsignedByteAt(index: Int): Int {
        //Util.Validation.checkIndexBounds(length(), index, 1, "unsigned byte")
        return 0xff and internalArray()[index].toInt()
    }

    /**
     * Returns the `char` value at the specified index.
     * Reads the primitive from given index and the following byte and interprets it according to byte order.
     *
     * @param index the index of the `char` value.
     * @return the `char` value at the specified index of the underlying byte array.
     * @throws IndexOutOfBoundsException if the `index` argument is negative or length is greater than index - 2
     */
    fun charAt(index: Int): Char {
        return internalBuffer().peck(index){ char }
//        Util.Validation.checkIndexBounds(length(), index, 2, "char")
//        return internalBuffer().position(index).getChar()
    }

    /**
     * Returns the `short` value at the specified index.
     * Reads the primitive from given index and the following byte and interprets it according to byte order.
     *
     * @param index the index of the `short` value.
     * @return the `short` value at the specified index of the underlying byte array.
     * @throws IndexOutOfBoundsException if the `index` argument is negative or length is greater than index - 2
     */
    fun shortAt(index: Int): Short {
        return internalBuffer().peck(index){ short }
//        Util.Validation.checkIndexBounds(length(), index, 2, "short")
//        return internalBuffer().position(index).getShort()
    }

    /**
     * Returns the `int` value at the specified index.
     * Reads the primitive from given index and the following 3 bytes and interprets it according to byte order.
     *
     * @param index the index of the `int` value.
     * @return the `int` value at the specified index of the underlying byte array.
     * @throws IndexOutOfBoundsException if the `int` argument is negative or length is greater than index - 4
     */
    fun intAt(index: Int): Int {
        return internalBuffer().peck(index){ int }
//        Util.Validation.checkIndexBounds(length(), index, 4, "int")
//        return internalBuffer().position(index).getInt()
    }

    /**
     * Returns the `long` value at the specified index.
     * Reads the primitive from given index and the following 7 bytes and interprets it according to byte order.
     *
     * @param index the index of the `long` value.
     * @return the `long` value at the specified index of the underlying byte array.
     * @throws IndexOutOfBoundsException if the `long` argument is negative or length is greater than index - 8
     */
    fun longAt(index: Int): Long {
        return internalBuffer().peck(index){ long }
//        Util.Validation.checkIndexBounds(length(), index, 8, "long")
//        return internalBuffer().position(index).getLong()
    }

    /**
     * Traverses the internal byte array counts the occurrences of given byte.
     * This has a time complexity of O(n).
     *
     * @param target byte to count
     * @return the count of given target in the byte array
     */
    fun count(target: Byte): Int {
        return internalArray().count { it == target  }
    }

    /**
     * Traverses the internal byte array counts the occurrences of given pattern array.
     * This has a time complexity of O(n).
     *
     *
     * Example:
     *
     *  * Internal Array: [0, 1, 2, 0, 1, 0]
     *  * Pattern Array: [0, 1]
     *  * Count: 2
     *
     *
     * @param pattern byte array to count
     * @return the count of given target in the byte array
     */
    fun count(pattern: ByteArray): Int {
        return internalArray().countByteArray(pattern)
    }

    /**
     * Calculates the entropy of the internal byte array. This might be useful for judging the internal data
     * for using e.g. in security relevant use case. In statistical mechanics, entropy is related to the number of
     * microscopic configurations Ω that a thermodynamic system can have when in a state as specified by some macroscopic
     * variables. Specifically, assuming for simplicity that each of the microscopic configurations is equally probable,
     * the entropy of the system is the natural logarithm of that number of configurations, multiplied by the Boltzmann constant kB.
     *
     *
     * This implementation requires O(n) time and O(1) space complexity.
     *
     * @return entropy value; higher is more entropy (simply: more different values)
     * @see [Entropy](https://en.wikipedia.org/wiki/Entropy)
     */
    fun entropy(): Double {
        return internalArray().entropy()
    }

    /* CONVERTERS POSSIBLY REUSING THE INTERNAL ARRAY ***************************************************************/
    /**
     * Create a new instance which shares the same underlying array
     *
     * @return new instance backed by the same data
     */
    fun duplicate(): Bytes {
        return Bytes(internalArray(), byteOrder)
    }

    /**
     * Set the byte order or endianness of this instance. Default in Java is [ByteOrder.BIG_ENDIAN].
     *
     *
     * This option is important for all encoding and conversion methods.
     *
     * @param byteOrder new byteOrder
     * @return a new instance with the same underlying array and new order, or "this" if order is the same
     * @see [Endianness](https://en.wikipedia.org/wiki/Endianness)
     */
    fun byteOrder(byteOrder: ByteOrder): Bytes {
        if (byteOrder != this.byteOrder) {
            return wrap(internalArray(), byteOrder)
        }
        return this
    }

    /**
     * Returns a new read-only byte instance. Read-only means, that there is no direct access to the underlying byte
     * array and all transformers will create a copy.
     *
     * @return a new instance if not already readonly, or "this" otherwise
     */
//    fun readOnly(): ReadOnlyBytes? {
//        if (this.isReadOnly) {
//            return this as ReadOnlyBytes
//        } else {
//            return ReadOnlyBytes(internalArray(), byteOrder)
//        }
//    }

    /**
     * The internal byte array wrapped in a [ByteBuffer] instance.
     * Changes to it will be directly mirrored in this [Bytes] instance.
     *
     *
     * This will honor the set [.byteOrder].
     *
     * @return byte buffer
     * @throws ReadOnlyBufferException if this is a read-only instance
     */
    fun buffer(): ByteBuffer {
        return ByteBuffer(array(),byteOrder)
    }

    private fun internalBuffer(): ByteBuffer {
        return ByteBuffer(internalArray(),byteOrder)
    }

    /**
     * Returns a mutable version of this instance with sharing the same underlying byte-array.
     * If you want the mutable version to be a copy, call [.copy] first.
     *
     * @return new mutable instance with same reference to internal byte array, or "this" if this is already of type [MutableBytes]
     * @throws ReadOnlyBufferException if this is a read-only instance
     */
//    fun mutable(): MutableBytes? {
//        if (this is MutableBytes) {
//            return this as MutableBytes
//        } else {
//            return MutableBytes(array(), byteOrder)
//        }
//    }

    /**
     * Creates an input stream with the same backing data as the intern array of this instance
     *
     * @return new input stream
     */
//    fun inputStream(): InputStream? {
//        return ByteArrayInputStream(array())
//    }

    /**
     * The reference of te internal byte-array. This call requires no conversation or additional memory allocation.
     *
     *
     * Modifications to these byte's content will cause the returned
     * array's content to be modified, and vice versa.
     *
     * @return the direct reference of the internal byte array
     * @throws ReadOnlyBufferException if this is a read-only instance
     */
    fun array(): ByteArray {
        return internalArray()
    }

    fun internalArray(): ByteArray {
        return byteArray
    }

    /* ENCODER ************************************************************************************************/
    /**
     * Binary (aka "1" and "0") representation. This is especially useful for debugging purposes.
     * Binary has a space efficiency of 12.5%.
     *
     *
     * Example: `10011100`
     *
     * @return binary string
     * @see [Binary number](https://en.wikipedia.org/wiki/Binary_number)
     */
//    fun encodeBinary(): String {
//        return encodeRadix(2)
//    }

    /**
     * Octal (0-7) representation. Octal has a space efficiency of 37.5%.
     *
     *
     * Example: `1124517677707527755`
     *
     * @return octal number as string
     * @see [Octal](https://en.wikipedia.org/wiki/Octal)
     */
//    fun encodeOctal(): String {
//        return encodeRadix(8)
//    }

    /**
     * Decimal (0-9) representation. It has a space efficiency of 41.5%.
     *
     *
     * Example: `20992966904426477`
     *
     * @return decimal number as string
     * @see [Decimal](https://en.wikipedia.org/wiki/Decimal)
     */
//    fun encodeDec(): String {
//        return encodeRadix(10)
//    }

    /**
     * Encodes the internal array in given radix representation (e.g. 2 = binary, 10 = decimal, 16 = hex).
     *
     *
     * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation
     * tries to preserve the leading zeros, to keep the in/output byte length size the same. To preserve the length padding
     * would be required, but is not supported in this implementation.
     *
     *
     * But still full disclaimer:
     *
     *
     * **This is NOT recommended for data encoding, only for number encoding**
     *
     *
     * See [Radix Economy](https://en.wikipedia.org/wiki/Radix_economy) and [BigInteger.toString].
     *
     * @param radix of the String representation (supported are 2-36)
     * @return string in given radix representation
     */
//    fun encodeRadix(radix: Int): String {
//        return encode(BaseRadixNumber(radix))
//    }

    /**
     * Base16 or Hex representation. See [.encodeHex].
     *
     *
     * Example: `4A94FDFF1EAFED`
     *
     * @param upperCase if the output character should be in uppercase
     * @return hex string
     */
    /**
     * Base16 or Hex representation in lowercase. 2 characters represent a single byte, it therefore has an efficiency of 50%.
     *
     *
     * Example: `4a94fdff1eafed`
     *
     * @return hex string
     * @see [Hexadecimal](https://en.wikipedia.org/wiki/Hexadecimal)
     */
//    @JvmOverloads
//    fun encodeHex(upperCase: Boolean = false): String {
//        return encode(Hex(upperCase))
//    }

    /**
     * Base32 RFC4648 string representation of the internal byte array (not Base32 hex alphabet extension)
     *
     *
     * Example: `MZXW6YQ=`
     *
     *
     * See [RFC 4648](https://tools.ietf.org/html/rfc4648)
     *
     * @return base32 string
     */
//    fun encodeBase32(): String {
//        return encode(BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING))
//    }



    /**
     * Base64 representation with padding. This is the url safe variation substitution '+' and '/' with '-' and '_'
     * respectively. This encoding has a space efficiency of 75%.
     *
     *
     * This encoding is [RFC 4648](https://tools.ietf.org/html/rfc4648) compatible.
     *
     *
     * Example: `SpT9_x6v7Q==`
     *
     * @return base64 url safe string
     * @see [Base64](https://en.wikipedia.org/wiki/Base64)
     */
//    fun encodeBase64Url(): String {
//        return encodeBase64(true, true)
//    }

    /**
     * Base64 representation with either padding or without and with or without URL and filename safe alphabet.
     * This encoding is [RFC 4648](https://tools.ietf.org/html/rfc4648) compatible.
     *
     *
     * Example: `SpT9/x6v7Q==`
     *
     * @param urlSafe     if true will substitute '+' and '/' with '-' and '_'
     * @param withPadding if true will add padding the next full byte with '='
     * @return base64 url safe string
     * @see [Base64](https://en.wikipedia.org/wiki/Base64)
     */
    /**
     * Base64 representation with padding. This is *NOT* the url safe variation. This encoding has a space efficiency of 75%.
     *
     *
     * This encoding is [RFC 4648](https://tools.ietf.org/html/rfc4648) compatible.
     *
     *
     * Example: `SpT9/x6v7Q==`
     *
     * @return base64 string
     * @see [Base64](https://en.wikipedia.org/wiki/Base64)
     */
//    @JvmOverloads
//    fun encodeBase64(urlSafe: Boolean = false, withPadding: Boolean = true): String {
//        return encode(Base64Encoding(urlSafe, withPadding))
//    }

    /**
     * UTF-8 representation of this byte array
     *
     * @return utf-8 encoded string
     * @see [UTF-8](https://en.wikipedia.org/wiki/UTF-8)
     */
//    fun encodeUtf8(): String {
//        return encodeCharset(StandardCharsets.UTF_8)
//    }

    /**
     * String representation with given charset encoding
     *
     * @param charset the charset the return will be encoded
     * @return encoded string
     */
    fun encodeCharset(charset: Charset): String {
        return charset.decode(internalArray()).toString()
        //return String(internalArray(), charset, "given charset must not be null"))
    }

    /**
     * UTF-8 representation of this byte array as byte array
     *
     *
     * Similar to `encodeUtf8().getBytes(StandardCharsets.UTF_8)`.
     *
     * @return utf-8 encoded byte array
     * @see [UTF-8](https://en.wikipedia.org/wiki/UTF-8)
     */
//    fun encodeUtf8ToBytes(): ByteArray? {
//        return encodeCharsetToBytes(StandardCharsets.UTF_8)
//    }

    /**
     * Byte array representation with given charset encoding.
     *
     *
     * Similar to `encodeCharset(charset).getBytes(charset)`.
     *
     * @param charset the charset the return will be encoded
     * @return encoded byte array
     */
//    fun encodeCharsetToBytes(charset: Charset?): ByteArray? {
//        return encodeCharset(charset).getBytes(charset)
//    }

    /**
     * Encode the internal byte-array with given encoder.
     *
     * @param encoder the encoder implementation
     * @return byte-to-text representation
     */
//    fun encode(encoder: BinaryToTextEncoding.Encoder): String {
//        return encoder.encode(internalArray(), byteOrder)
//    }

    /* CONVERTERS WITHOUT REUSING THE INTERNAL ARRAY ****************************************************************/
    /**
     * Returns a copy of the internal byte-array as [List] collection type
     * This requires a time and space complexity of O(n).
     *
     * @return copy of internal array as list
     */
    fun toList() = internalArray().toList()


    /**
     * Returns a copy of the internal byte-array as boxed primitive array.
     * This requires a time and space complexity of O(n).
     *
     *
     * Note: this method was previously called `toObjectArray()`
     *
     * @return copy of internal array as object array
     */
    fun toBoxedArray(): Array<Byte> {
        return internalArray().toTypedArray()
    }

    /**
     * Returns a copy of the internal byte-array as [BitSet] type
     *
     * @return bit set with the content of the internal array
     */
//    fun toBitSet(): BitSet {
//        return BitSet.valueOf(internalArray())
//    }

    /**
     * The internal byte array wrapped in a [BigInteger] instance.
     *
     *
     * If the internal byte order is [ByteOrder.LITTLE_ENDIAN], a copy of the internal
     * array will be reversed and used as backing array with the big integer. Otherwise, the internal
     * array will be used directly.
     *
     * @return big integer
     */
//    fun toBigInteger(): BigInteger {
//        if (byteOrder == ByteOrder.LittleEndian) {
//            return BigInteger(BytesTransformer.ReverseTransformer().transform(internalArray(), false))
//        } else {
//            return BigInteger(internalArray())
//        }
//    }

    /**
     * Creates a [UUID] instance of the internal byte array. This requires the internal array to be exactly 16 bytes. Takes the first
     * 8 byte as mostSigBits and the last 8 byte as leastSigBits. There is no validation of version/type, just passes the raw bytes
     * to a [UUID] constructor.
     *
     * @return newly created UUID
     * @throws IllegalArgumentException if byte array has length not equal to 16
     */
//    fun toUUID(): UUID? {
//        Util.Validation.checkExactLength(length(), 16, "UUID")
//        val byteBuffer = buffer()
//        return UUID(byteBuffer.long, byteBuffer.long)
//    }

    /**
     * If the underlying byte array is exactly 1 byte / 8 bit long, returns signed two-complement
     * representation for a Java byte value.
     *
     *
     * If you just want to get the first element as `byte`, see [.byteAt], using index zero.
     *
     * @return the byte representation
     * @throws IllegalArgumentException if byte array has length not equal to 1
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toByte(): Byte {
        require(length()==1)
        return internalArray()[0]
    }

    /**
     * If the underlying byte array is exactly 1 byte / 8 bit long, returns unsigned two-complement
     * representation for a Java byte value wrapped in an 4 byte int.
     *
     *
     * If you just want to get the first element as `byte`, see [.byteAt], using index zero.
     *
     * @return the unsigned byte representation wrapped in an int
     * @throws IllegalArgumentException if byte array has length not equal to 1
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toUnsignedByte(): Int {
        require(length()==1)
        return unsignedByteAt(0)
    }

    /**
     * If the underlying byte array is exactly 2 byte / 16 bit long, return unsigned two-complement
     * representation for a Java char integer value. The output is dependent on the set [.byteOrder].
     *
     *
     * If you just want to get the first 2 bytes as `char`, see [.charAt] using index zero.
     *
     * @return the int representation
     * @throws IllegalArgumentException if byte array has length not equal to 2
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toChar(): Char {
        require(length()==2)
        return charAt(0)
    }

    /**
     * If the underlying byte array is exactly 2 byte / 16 bit long, return signed two-complement
     * representation for a Java short integer value. The output is dependent on the set [.byteOrder].
     *
     *
     * If you just want to get the first 2 bytes as `short`, see [.shortAt] using index zero.
     *
     * @return the int representation
     * @throws IllegalArgumentException if byte array has length not equal to 2
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toShort(): Short {
        require(length()==2)
        return shortAt(0)
    }

    /**
     * If the underlying byte array is exactly 4 byte / 32 bit long, return signed two-complement
     * representation for a Java signed integer value. The output is dependent on the set [.byteOrder].
     *
     *
     * If you just want to get the first 4 bytes as `int`, see [.intAt] using index zero.
     *
     * @return the int representation
     * @throws IllegalArgumentException if byte array has length not equal to 4
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toInt(): Int {
        require(length()==4)
        return intAt(0)
    }

    /**
     * Converts the internal byte array to an int array, that is, every 4 bytes will be packed into a single int.
     *
     *
     * E.g. 4 bytes will be packed to a length 1 int array:
     * <pre>
     * [b1, b2, b3, b4] = [int1]
    </pre> *
     *
     *
     * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to int,
     * which means the byte array length must be dividable by 4 without rest.
     *
     * @return new int[] instance representing this byte array
     * @throws IllegalArgumentException if internal byte length mod 4 != 0
     */
    fun toIntArray(): IntArray {
        require( length()%4 == 0 )
        return internalArray().toIntArray(byteOrder)//, byteOrder)
    }

    /**
     * If the underlying byte array is exactly 8 byte / 64 bit long, return signed two-complement
     * representation for a Java signed long integer value. The output is dependent on the set [.byteOrder].
     *
     *
     * If you just want to get the first 4 bytes as `long`, see [.longAt] using index zero.
     *
     * @return the long representation
     * @throws IllegalArgumentException if byte array has length not equal to 8
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toLong(): Long {
        require(length()==8)
        return longAt(0)
    }

    /**
     * Converts the internal byte array to a long array, that is, every 8 bytes will be packed into a single long.
     *
     *
     * E.g. 8 bytes will be packed to a length 1 long array:
     * <pre>
     * [b1, b2, b3, b4, b5, b6, b7, b8] = [int1]
    </pre> *
     *
     *
     * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to long,
     * which means the byte array length must be dividable by 8 without rest.
     *
     * @return new long[] instance representing this byte array
     * @throws IllegalArgumentException if internal byte length mod 8 != 0
     */
    fun toLongArray(): LongArray {
        return internalArray().toLongArray()
//        Util.Validation.checkModLength(length(), 8, "creating an long array")
//        return Util.Converter.toLongArray(internalArray(), byteOrder)
    }

    /**
     * If the underlying byte array is exactly 4 byte / 32 bit long, return the
     * representation for a Java float value. The output is dependent on the set [.byteOrder].
     *
     * @return the float representation
     * @throws IllegalArgumentException if byte array has length not equal to 4
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toFloat(): Float {
        return internalBuffer().peck(0){ float }
//        Util.Validation.checkExactLength(length(), 4, "float")
//        return internalBuffer().float
    }

    /**
     * Converts the internal byte array to a float array, that is, every 4 bytes will be packed into a single float.
     *
     *
     * E.g. 4 bytes will be packed to a length 1 float array:
     * <pre>
     * [b1, b2, b3, b4] = [float1]
    </pre> *
     *
     *
     * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to float,
     * which means the byte array length must be dividable by 4 without rest.
     *
     * @return new float[] instance representing this byte array
     * @throws IllegalArgumentException if internal byte length mod 4 != 0
     */
    fun toFloatArray(): FloatArray {
        return internalArray().toFloatArray()
//        Util.Validation.checkModLength(length(), 4, "creating an float array")
//        return Util.Converter.toFloatArray(internalArray(), byteOrder)
    }

    /**
     * If the underlying byte array is exactly 8 byte / 64 bit long, return the
     * representation for a Java double value. The output is dependent on the set [.byteOrder].
     *
     * @return the double representation
     * @throws IllegalArgumentException if byte array has length not equal to 8
     * @see [Primitive Types](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html)
     */
    fun toDouble(): Double {
        return internalBuffer().peck(0){ double }
//        Util.Validation.checkExactLength(length(), 8, "double")
//        return internalBuffer().double
    }

    /**
     * Converts the internal byte array to a double array, that is, every 8 bytes will be packed into a single double.
     *
     *
     * E.g. 8 bytes will be packed to a length 1 double array:
     * <pre>
     * [b1, b2, b3, b4, b5, b6, b7, b8] = [double1]
    </pre> *
     *
     *
     * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to double,
     * which means the byte array length must be dividable by 8 without rest.
     *
     * @return new double[] instance representing this byte array
     * @throws IllegalArgumentException if internal byte length mod 8 != 0
     */
    fun toDoubleArray(): DoubleArray {
        return internalArray().toDoubleArray()
//        Util.Validation.checkModLength(length(), 8, "creating an double array")
//        return Util.Converter.toDoubleArray(internalArray(), byteOrder)
    }

    /**
     * Converts the internal byte array to a short array, that is, every 2 bytes will be packed into a single short.
     *
     *
     * E.g. 2 bytes will be packed to a length 1 short array:
     * <pre>
     * [b1, b2] = [short1]
    </pre> *
     *
     *
     * This conversion respects the internal byte order. Will only work if all bytes can be directly mapped to short,
     * which means the byte array length must be dividable by 2 without rest.
     *
     * @return new short[] instance representing this byte array
     * @throws IllegalArgumentException if internal byte length mod 2 != 0
     */
    fun toShortArray(): ShortArray {
        return internalArray().toShortArray()
//        Util.Validation.checkModLength(length(), 2, "creating a short array")
//        return Util.Converter.toShortArray(internalArray(), byteOrder)
    }

    /**
     * Decodes the internal byte array with given charset to a char array.
     * This implementation will not internally create a [String].
     *
     * @param charset to use for decoding
     * @return char array
     */
    /**
     * Decodes the internal byte array to UTF-8 char array.
     * This implementation will not internally create a [String].
     *
     * @return char array
     */
    @JvmOverloads
    fun toCharArray(charset: Charset = Charset.UTF8): CharArray {
        return internalArray().decodeToString(charset).toCharArray()
    }

    /**
     * Compares this bytes instance to another.
     *
     *
     * Two byte bytes are compared by comparing their sequences of
     * remaining elements lexicographically, without regard to the starting
     * position of each sequence within its corresponding buffer.
     * Pairs of `byte` elements are compared as if by invoking
     * [Byte.compare].
     *
     *
     * Uses [ByteBuffer.compareTo] internally.
     *
     * @return A negative integer, zero, or a positive integer as this buffer
     * is less than, equal to, or greater than the given buffer
     */
    override fun compareTo(o: Bytes): Int {
        return internalBuffer().compareTo(o.internalBuffer())
    }

    /**
     * Checks if this instance is equal to given other instance o
     *
     * @param o other instance
     * @return if the instance are equal
     */
    override fun equals(o: Any?): Boolean {
        if (this === o) return true
        if (o == null || this::class != o::class) return false

        val bytes = o as Bytes

        if (!Objects.equals(byteArray, bytes.byteArray)) return false
        return equals<ByteOrder>(byteOrder, bytes.byteOrder)
    }

    /**
     * Compares the inner array with given array
     *
     * @param anotherArray to compare with
     * @return true if [Arrays.equals] returns true on given and internal array
     */
    fun equals(anotherArray: ByteArray?): Boolean {
        return anotherArray != null && internalArray().contentEquals(anotherArray)
    }

    /**
     * Compares the inner array with given array. The comparison is done in constant time, therefore
     * will not break on the first mismatch. This method is useful to prevent some side-channel attacks,
     * but is slower on average.
     *
     *
     * This implementation uses the algorithm suggested in [a-lesson-in-timing-attacks](https://codahale.com/a-lesson-in-timing-attacks/)
     *
     * @param anotherArray to compare with
     * @return true if [Arrays.equals] returns true on given and internal array
     */
    fun equalsConstantTime(anotherArray: ByteArray?): Boolean {
        return anotherArray != null && internalArray().constantTimeEquals( anotherArray)
    }

    /**
     * Compares the inner array with given array.
     * Note: a `null` Byte will not be equal to a `0` byte
     *
     * @param anotherArray to compare with
     * @return true if both array have same length and every byte element is the same
     */
    fun equals(anotherArray: Array<Byte>): Boolean {
        return internalArray().contentEquals(anotherArray.toByteArray())
    }

    /**
     * Compares the inner array with the inner array of given ByteBuffer.
     * Will check for internal array and byte order.
     *
     * @param buffer to compare with
     * @return true if both array have same length and every byte element is the same
     */
    fun equals(buffer: ByteBuffer?): Boolean {
        return buffer != null && byteOrder == buffer.order && internalBuffer() == buffer
    }

    /**
     * Checks only for internal array content
     *
     * @param other to compare to
     * @return true if the internal array are equals (see [Arrays.equals])
     */
    fun equalsContent(other: Bytes?): Boolean {
        return other != null && internalArray().contentEquals(other.internalArray())
    }

    override fun hashCode(): Int {
        return Objects.hashCode(internalArray(), byteOrder)
    }

    /**
     * A constant length output toString() implementation, which only shows the byte length and at most 8 bytes preview in hex
     * representation.
     *
     * @return string representation
     */
    override fun toString(): String {
        return internalArray().decodeToString()
    }

    override fun iterator(): Iterator<Byte> {
        return BytesIterator(internalArray())
    }

    /**
     * Internal factory for [Bytes] instances
     */
//    private class Factory : BytesFactory {
//        public override fun wrap(array: ByteArray, byteOrder: ByteOrder): Bytes {
//            return Bytes(array, byteOrder)
//        }
//    }

    /**
     * Creates a new immutable instance
     *
     * @param byteArray internal byte array
     * @param byteOrder the internal byte order - this is used to interpret given array, not to change it
     */


    companion object {
        private val EMPTY = wrap(ByteArray(0))

        /**
         * Creates a new instance with an empty array filled with given defaultValue
         *
         * @param length       of the internal array
         * @param defaultValue to fill with
         * @return new instance
         */
        /* FACTORY ***************************************************************************************************/
        /**
         * Creates a new instance with an empty array filled with zeros.
         *
         * @param length of the internal array
         * @return new instance
         */
        @JvmOverloads
        fun allocate(length: Int, defaultValue: Byte = 0.toByte()): Bytes {
            if (length == 0) return empty()
            val array = ByteArray(length){defaultValue}
            return wrap(array)
        }

        /**
         * Creates a Byte instance with an internal empty byte array. Same as calling [.allocate] with 0.
         *
         * @return the empty instance (always the same reference
         */
        fun empty(): Bytes {
            return EMPTY
        }

        /**
         * Creates a new reference backed by the same byte array.
         * Inherits all attributes (readonly, etc.)
         *
         * @param bytes to use as template
         * @return new instance
         */
        fun wrap(bytes: Bytes): Bytes {
            return wrap(
                bytes.internalArray(),
                bytes.byteOrder
            )
        }

        /**
         * Creates a new instance with given byte array.
         *
         *
         * The new instance will be backed by the given byte array;
         * that is, modifications to the bytes will cause the array to be modified
         * and vice versa.
         *
         *
         * If given array is null, a zero length byte array will be created and used instead.
         *
         * @param array to use directly or zero length byte array
         * @return new instance
         */
        fun wrapNullSafe(array: ByteArray?): Bytes {
            return if (array != null) wrap(array) else empty()
        }

        /**
         * Creates a new instance with given byte array.
         *
         *
         * The new instance will be backed by the given byte array;
         * that is, modifications to the bytes will cause the array to be modified
         * and vice versa.
         *
         * @param array     to use directly
         * @param byteOrder the byte order of passed array
         * @return new instance
         */
        /**
         * Creates a new instance with given byte array.
         *
         *
         * The new instance will be backed by the given byte array;
         * that is, modifications to the bytes will cause the array to be modified
         * and vice versa.
         *
         * @param array to use directly
         * @return new instance
         */
        @JvmOverloads
        fun wrap(array: ByteArray, byteOrder: ByteOrder = ByteOrder.BigEndian): Bytes {
            return Bytes(array,  byteOrder)
        }

        /**
         * Creates a new instance from given collections of single bytes.
         * This will create a copy of given bytes and will not directly use given bytes or byte array.
         *
         * @param byteArrayToCopy must not be null and will not be used directly, but a copy
         * @return new instance
         */
        fun from(byteArrayToCopy: ByteArray): Bytes {
            return wrap(byteArrayToCopy)
        }

        /**
         * Creates a new instance from given collections of single bytes.
         * This will create a copy of given bytes and will not directly use given bytes or byte array.
         *
         *
         * If given array is null, a zero length byte array will be created and used instead.
         *
         * @param byteArrayToCopy will not be used directly, but a copy; may be null
         * @return new instance
         */
        fun fromNullSafe(byteArrayToCopy: ByteArray?): Bytes {
            return if (byteArrayToCopy != null) from(byteArrayToCopy) else empty()
        }

        /**
         * Creates a new instance from a slice of given array
         *
         * @param array  to slice
         * @param offset start position
         * @param length length
         * @return new instance
         */
        fun from(array: ByteArray, offset: Int, length: Int): Bytes {
            val part = ByteArray(length)
            arraycopy(array, offset, part, 0, length)
            return wrap(part)
        }

        /**
         * Creates a new instance from given array of byte arrays
         *
         * @param moreArrays must not be null
         * @return new instance
         */
        fun from(vararg moreArrays: ByteArray): Bytes {
            return wrap(combineToByteArray( moreArrays))
        }

        /**
         * Creates a new instance from given array of byte arrays
         *
         * @param moreBytes must not be null
         * @return new instance
         */
        fun from(vararg moreBytes: Bytes): Bytes {
            return wrap(combineToByteArray(moreBytes.map { it.internalArray() }))

//            val bytes = ByteArray(moreBytes.size){i-> moreBytes[i].array() }
//            for (i in moreBytes.indices) {
//                bytes[i] = moreBytes[i].array()
//            }
//            return from(bytes)
        }

        /**
         * Creates a new instance from given collections. This will create a lot of auto-unboxing events,
         * so use with care with bigger lists.
         *
         * @param bytesCollection to create from
         * @return new instance
         */
        fun from(bytesCollection: Collection<Byte>): Bytes? {
            return wrap(bytesCollection.toByteArray())
        }

        /**
         * Creates a new instance from given object byte array. Will copy and unbox every element.
         *
         * @param boxedObjectArray to create from
         * @return new instance
         */
        fun from(boxedObjectArray: Array<Byte>): Bytes {
            return wrap(boxedObjectArray.toByteArray())
        }

        /**
         * Creates a new single array element array instance from given byte
         *
         * @param singleByte to create from
         * @return new instance
         */
//        fun from(singleByte: Byte): Bytes {
//            return wrap(byteArrayOf(singleByte))
//        }

        /**
         * Creates a new instance from given collections of single bytes.
         * This will create a copy of given bytes and will not directly use given bytes or byte array.
         *
         * @param firstByte must not be null and will not be used directly, but a copy
         * @param moreBytes more bytes vararg
         * @return new instance
         */
        fun from(firstByte: Byte, vararg moreBytes: Byte): Bytes {
            return wrap(byteArrayOf(firstByte, *moreBytes))
        }

        /**
         * Creates a new instance from given boolean.
         * This will create a new single array element array instance using the convention that false is zero.
         * E.g. Creates array `new byte[] {1}` if booleanValue is true and `new byte[] {0}` if
         * booleanValue is false.
         *
         * @param booleanValue to convert (false is zero, true is one)
         * @return new instance
         */
        fun from(booleanValue: Boolean): Bytes {
            return wrap(byteArrayOf(if (booleanValue) 1.toByte() else 0))
        }

        /**
         * Creates a new instance from given unsigned 2 byte char.
         *
         * @param char2Byte to create from
         * @return new instance
         */
        fun from(char2Byte: Char): Bytes {
            return wrap(char2Byte.code.toByteArray())
        }

        /**
         * Creates a new instance from given 2 byte short.
         *
         * @param short2Byte to create from
         * @return new instance
         */
        fun from(short2Byte: Short): Bytes {
            return wrap(short2Byte.toByteArray())
        }

        /**
         * Creates a new instance from given 2 byte short array.
         *
         * @param shortArray to create from
         * @return new instance
         */
        fun from(vararg shortArray: Short): Bytes {
            return wrap(shortArray.toByteArray())
//            return wrap(
//                Util.Converter.toByteArray(
//                    Objects.requireNonNull(
//                        shortArray,
//                        "must provide at least a single short"
//                    )
//                )
//            )
        }

        /**
         * Creates a new instance from given 4 byte integer.
         *
         * @param integer4byte to create from
         * @return new instance
         */
        fun from(integer4byte: Int): Bytes {
            return wrap(integer4byte.toByteArray())
            //return wrap(ByteBuffer.allocate(4).putInt(integer4byte).array())
        }

        /**
         * Creates a new instance from given 4 byte integer array.
         *
         * @param intArray to create from
         * @return new instance
         */
        fun from(vararg intArray: Int): Bytes {
            return wrap(intArray.toByteArray())
//            return wrap(
//                Util.Converter.toByteArray(
//                    Objects.requireNonNull(
//                        intArray,
//                        "must provide at least a single int"
//                    )
//                )
//            )
        }

        /**
         * Creates a new instance from given 8 byte long.
         *
         * @param long8byte to create from
         * @return new instance
         */
//        fun from(long8byte: Long): Bytes {
//            return wrap(ByteBuffer.allocate(8).putLong(long8byte).array())
//        }

        /**
         * Creates a new instance from given 8 byte long array.
         *
         * @param longArray to create from
         * @return new instance
         */
        fun from(vararg longArray: Long): Bytes {
            return wrap(longArray.toByteArray())
//            return wrap(
//                Util.Converter.toByteArray(
//                    Objects.requireNonNull(
//                        longArray,
//                        "must provide at least a single long"
//                    )
//                )
//            )
        }

        /**
         * Creates a new instance from given 4 byte floating point number (float).
         *
         * @param float4byte to create from
         * @return new instance
         */
//        fun from(float4byte: Float): Bytes? {
//            return wrap(ByteBuffer.allocate(4).putFloat(float4byte).array())
//        }

        /**
         * Creates a new instance from given float array.
         *
         * @param floatArray to create from
         * @return new instance
         */
        fun from(vararg floatArray: Float): Bytes {
            return wrap(floatArray.toByteArray())
//            return wrap(
//                Util.Converter.toByteArray(
//                    Objects.requireNonNull(
//                        floatArray,
//                        "must provide at least a single float"
//                    )
//                )
//            )
        }

        /**
         * Creates a new instance from given 8 byte floating point number (double).
         *
         * @param double8Byte to create from
         * @return new instance
         */
//        fun from(double8Byte: Double): Bytes? {
//            return wrap(ByteBuffer.allocate(8).putDouble(double8Byte).array())
//        }

        /**
         * Creates a new instance from given double array.
         *
         * @param doubleArray to create from
         * @return new instance
         */
        fun from(vararg doubleArray: Double): Bytes {
            return wrap(doubleArray.toByteArray())
//            return wrap(
//                Util.Converter.toByteArray(
//                    Objects.requireNonNull(
//                        doubleArray,
//                        "must provide at least a single double"
//                    )
//                )
//            )
        }

        /**
         * Creates a new instance from given [ByteBuffer].
         * Will use the same backing byte array and honour the buffer's byte order.
         *
         * @param buffer to get the byte array from (must not be null)
         * @return new instance
         */
        fun from(buffer: ByteBuffer): Bytes {
            return wrap(buffer.contentBytes, buffer.order)
        }

        /**
         * Creates a new instance from given [CharBuffer].
         * Will ignore buffer's byte order and use [ByteOrder.BIG_ENDIAN]
         *
         * @param buffer to get the char array from (must not be null)
         * @return new instance
         */
//        fun from(buffer: CharBuffer): Bytes? {
//            return from(Objects.requireNonNull(buffer, "provided char buffer must not be null").array())
//        }

        /**
         * Creates a new instance from given [IntBuffer].
         * Will ignore buffer's byte order and use [ByteOrder.BIG_ENDIAN]
         *
         * @param buffer to get the int array from (must not be null)
         * @return new instance
         */
//        fun from(buffer: IntBuffer): Bytes {
//            return from(Objects.requireNonNull(buffer, "provided int buffer must not be null").array())
//        }

        /**
         * Creates a new instance from given [BitSet].
         *
         * @param set to get the byte array from
         * @return new instance
         */
//        fun from(set: BitSet): Bytes {
//            return wrap(set.toByteArray())
//        }

        /**
         * Creates a new instance from given [BigInteger].
         *
         * @param bigInteger to get the byte array from
         * @return new instance
         */
        fun from(bigInteger: BigInteger): Bytes {
            return wrap(bigInteger.toByteArray())
        }

        /**
         * Reads given whole input stream and creates a new instance from read data
         *
         * @param stream to read from
         * @return new instance
         */
//        fun from(stream: InputStream?): Bytes? {
//            return wrap(Util.File.readFromStream(stream, -1))
//        }

        /**
         * Reads given input stream up to maxLength and creates a new instance from read data.
         * Read maxLength is never longer than stream size (i.e. maxLength is only limiting, not assuring maxLength)
         *
         * @param stream    to read from
         * @param maxLength read to this maxLength or end of stream
         * @return new instance
         */
//        fun from(stream: InputStream?, maxLength: Int): Bytes? {
//            return wrap(Util.File.readFromStream(stream, maxLength))
//        }

        /**
         * Reads given [DataInput] and creates a new instance from read data
         *
         * @param dataInput to read from
         * @param length    how many bytes should be read
         * @return new instance
         */
//        fun from(dataInput: DataInput?, length: Int): Bytes? {
//            return wrap(Util.File.readFromDataInput(dataInput, length))
//        }

        /**
         * Reads given file and returns the byte content. Be aware that the whole file content will be loaded to
         * memory, so be careful what to read in.
         *
         * @param file to read from
         * @return new instance
         * @throws IllegalArgumentException if file does not exist
         * @throws IllegalStateException    if file could not be read
         */
//        fun from(file: File?): Bytes? {
//            return wrap(Util.File.readFromFile(file))
//        }

        /**
         * Reads given file and returns the byte content. Be aware that the whole defined file content will be loaded to
         * memory, so be careful what to read in. This uses [java.io.RandomAccessFile] under the hood.
         *
         * @param file   to read from
         * @param offset byte offset from zero position of the file
         * @param length to read from offset
         * @return new instance
         * @throws IllegalArgumentException if file does not exist
         * @throws IllegalStateException    if file could not be read
         */
//        fun from(file: File?, offset: Int, length: Int): Bytes? {
//            return wrap(Util.File.readFromFile(file, offset, length))
//        }

        /**
         * Creates a new instance from given utf-8 encoded string
         *
         * @param utf8String to get the internal byte array from
         * @return new instance
         */
//        fun from(utf8String: CharSequence): Bytes {
//            return from(utf8String, Charsets.UTF8)
//        }

        /**
         * Creates a new instance from normalized form of given utf-8 encoded string
         *
         * @param utf8String to get the internal byte array from
         * @param form       to normalize, usually you want [java.text.Normalizer.Form.NFKD] for compatibility
         * @return new instance
         */
//        fun from(utf8String: CharSequence?, form: Normalizer.Form?): Bytes? {
//            return from(Normalizer.normalize(utf8String, form), StandardCharsets.UTF_8)
//        }

        /**
         * Creates a new instance from given string
         *
         * @param string  to get the internal byte array from
         * @param charset used to decode the string
         * @return new instance
         */
        fun from(string: CharSequence, charset: Charset=Charset.UTF8): Bytes {
            return wrap(charset.encode(string))

//            return wrap(
//                Objects.requireNonNull(string, "provided string must not be null").toString()
//                    .getBytes(Objects.requireNonNull(charset, "provided charset must not be null"))
//            )
        }

        /**
         * Creates a new instance from given char array using utf-8 encoding
         *
         * @param charArray to get the internal byte array from
         * @return new instance
         */
//        fun from(charArray: CharArray): Bytes {
//            return from(charArray, StandardCharsets.UTF_8)
//        }

        /**
         * Creates a new instance from given char array. The array will be handles like an encoded string
         *
         * @param charArray to get the internal byte array from
         * @param charset   charset to be used to decode the char array
         * @return new instance
         */
        fun from(charArray: CharArray, charset: Charset): Bytes {
            return from(charArray, charset, 0, charArray.size)
        }

        /**
         * Creates a new instance from given char array with given range. The array will be handles like an encoded string
         *
         * @param charArray to get the internal byte array from
         * @param charset   charset to be used to decode the char array
         * @param offset    start position (from given char array not encoded byte array out)
         * @param length    length in relation to offset (from given char array not encoded byte array out)
         * @return new instance
         */
        fun from(charArray: CharArray, charset: Charset, offset: Int, length: Int): Bytes {
           return wrap(charset.encode(charArray.toString().subSequence(offset,offset+length)))
          //return from(Util.Converter.charToByteArray(charArray, charset, offset, length))
        }

        /**
         * Convert UUID to a newly generated 16 byte long array representation. Puts the 8 byte most significant bits and
         * 8 byte the least significant bits into a byte array.
         *
         * @param uuid to convert to array
         * @return new instance
         */
        @OptIn(ExperimentalUuidApi::class)
        fun from(uuid: Uuid): Bytes {
            return wrap(uuid.toByteArray())
        }

        /**
         * Parses a big endian binary string (e.g. `10010001`)
         *
         * @param binaryString the encoded string
         * @return decoded instance
         */
        fun parseBinary(binaryString: CharSequence): Bytes {
            return wrap(binaryString.toString().binary2ByteArray())
            //return parseRadix(binaryString, 2)
        }

        /**
         * Parsing of octal encoded byte arrays.
         *
         * @param octalString the encoded string
         * @return decoded instance
         */
//        fun parseOctal(octalString: CharSequence?): Bytes? {
//            return parseRadix(octalString, 8)
//        }

        /**
         * Parsing of decimal encoded byte arrays.
         *
         * @param decString the encoded string
         * @return decoded instance
         */
//        fun parseDec(decString: CharSequence?): Bytes? {
//            return parseRadix(decString, 10)
//        }

        /**
         * Encodes with given radix string representation (e.g. radix 16 would be hex).
         * See also [BigInteger.toString].
         *
         *
         * This is usually a number encoding, not a data encoding (i.e. leading zeros are not preserved), but this implementation
         * tries to preserve the leading zeros, to keep the in/output byte length size the same, but use at your own risk!
         *
         * @param radixNumberString the encoded string
         * @param radix             radix of the String representation (supported are 2-36)
         * @return decoded instance
         */
//        fun parseRadix(radixNumberString: CharSequence, radix: Int): Bytes? {
//            return parse(radixNumberString, BaseRadixNumber(radix))
//        }

        /**
         * Parsing of base16/HEX encoded byte arrays. This is by design a very flexible decoder accepting the following cases:
         *
         *
         *  * Upper- and lowercase `a-f` (also mixed case)
         *  * Prefix with `0x` which will be ignored
         *  * Even and odd number of string length with auto zero padding (i.e. 'E3F' is same as '0E3F')
         *
         *
         * @param hexString the encoded string
         * @return decoded instance
         * @throws IllegalArgumentException if string contains something else than [0-9a-fA-F]
         */
        fun parseHex(hexString: CharSequence): Bytes {
            return wrap(hexString.hexToByteArray())
        }

        /**
         * Parsing of base32/RFC 4648 encoded byte arrays.
         *
         *
         * Uses the RFC 4648 non-hex alphabet, see [Base32 alphabet](https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet).
         *
         * @param base32Rfc4648String the encoded string
         * @return decoded instance
         */
//        fun parseBase32(base32Rfc4648String: CharSequence?): Bytes? {
//            return parse(
//                base32Rfc4648String,
//                BaseEncoding(BaseEncoding.BASE32_RFC4848, BaseEncoding.BASE32_RFC4848_PADDING)
//            )
//        }



        /**
         * Parsing of base64 encoded byte arrays.
         * Supporting RFC 4648 normal and url safe encoding, with or without padding.
         *
         * @param base64String the encoded string
         * @return decoded instance
         */
        fun parseBase64(base64String: CharSequence): Bytes {
            return wrap(base64String.toString().decodeBase64())
        }

        /**
         * Parsing of arbitrary encoded format
         *
         * @param encoded the encoded string
         * @param decoder the decoder used to decode the string
         * @return decoded instance
         */
//        fun parse(encoded: CharSequence, decoder: BinaryToTextEncoding.Decoder?): Bytes? {
//            return wrap(
//                Objects.requireNonNull(decoder, "passed decoder instance must no be null")
//                    .decode(Objects.requireNonNull(encoded, "encoded data must not be null"))
//            )
//        }

        /**
         * A new instance with pseudo random bytes using an unsecure random number generator.
         * This may be used in e.g. tests. In production code use [.random] per default.
         *
         *
         * **ONLY USE IN NON-SECURITY RELEVANT CONTEXT!**
         *
         * @param length desired array length
         * @return random instance
         */
        fun unsecureRandom(length: Int): Bytes {
            return random(length, Random)
        }

        /**
         * A new instance with pseudo random bytes using an unsecure random number generator.
         * This may be used in e.g. tests to create predictable numbers.
         *
         *
         * In production code use [.random] per default.
         *
         *
         * **ONLY USE IN NON-SECURITY RELEVANT CONTEXT!**
         *
         * @param length desired array length
         * @param seed   used to seed random number generator - using same seed will generate same numbers
         * @return random instance
         */
        fun unsecureRandom(length: Int, seed: Long): Bytes {
            return random(length, Random(seed))
        }

        /**
         * A new instance with random bytes.
         *
         * @param length desired array length
         * @param random to create the entropy for the random bytes
         * @return random instance
         */
        /**
         * A new instance with random bytes. Uses a cryptographically secure [SecureRandom] instance.
         *
         * @param length desired array length
         * @return random instance
         */
        @JvmOverloads
        fun random(length: Int, random: Random = Random): Bytes {
            val array = ByteArray(length)
            random.nextBytes(array)
            return wrap(array)
        }

        const val serialVersionUID: Long = 1L
    }
}



/**
 * A simple iterator for the bytes class, which does not support remove
 */
internal  class BytesIterator(private val array: ByteArray) : kotlin.collections.Iterator<Byte> {
    /**
     * Index of element to be returned by subsequent call to next.
     */
    private var cursor = 0

    public override fun hasNext(): Boolean {
        return cursor != array.size
    }

    public override fun next(): Byte {
        try {
            val i = cursor
            val next = array[i]
            cursor = i + 1
            return next
        } catch (e: IndexOutOfBoundsException) {
            throw NoSuchElementException()
        }
    }

    fun remove() {
        throw UnsupportedOperationException("The Bytes iterator does not support removing")
    }
}

/**
 * Fills the internal byte array with random data provided by [SecureRandom]
 *
 * @return this instance
 */
fun Bytes.secureWipe(): Bytes {
    if (length() > 0)
       SecureRandom.nextBytes(internalArray())
    return this
}